﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace PdfLibrary
{
    public enum FileReaderParseDirection { Backward, Forward };

    public class FileReader
    {
        protected static byte[] newLineCharacters = new byte[] { 10, 13 };
        protected static byte[] whitespaceCharacters = new byte[] { 0, 9, 10, 12, 13, 32 };
        //protected static byte[] delimiterCharacters = new byte[] { 0, 9, 10, 12, 13, 32, 40, 41, 60, 62, 91, 93, 123, 125, 47, 37 };
        protected static byte[] delimiterCharacters = new byte[] { 0, 9, 10, 12, 13, (byte)' ', (byte)'(', (byte)')', (byte)'<', (byte)'>',
          (byte)'[', (byte)']', (byte)'{', (byte)'}', (byte)'/', (byte)'%' };

        protected byte[] content;

        protected int safeLastPosition = -1;

        protected int position;
        public int Position
        {
            get { return this.position; }
            set { this.position = value; }
        }

        protected string lastReadText;
        public string LastReadText { get { return this.lastReadText; } }

        protected FileReaderParseDirection parseDirection = FileReaderParseDirection.Forward;
        public FileReaderParseDirection ParseDirection
        {
            get { return this.parseDirection; }

            set
            {
                if (this.parseDirection != value)
                {
                    this.parseDirection = value;
                    // set the correct starting character for the direction search 
                    //   position always the character what should be processed next:
                    //   the change in direction, change this
                    this.position += this.SeekDirection;
                    // readline skip newline characters, at direction change, we need to jump back
                    this.SkipNewLineCharacters();
                }
            }
        }

        public FileReader(byte[] content)
        {
            this.content = content;
        }

        public void SeekToEnd()
        {
            this.Position = this.content.Length - 1;
        }

        public bool IsParsingBackward { get { return this.ParseDirection == FileReaderParseDirection.Backward; } }

        public int SeekDirection { get { return this.IsParsingBackward ? -1 : 1; } }

        public int Current
        {
            get
            {
                /*if (0 <= this.position && this.position < this.content.Length)*/
                    return this.content[this.position];
                /*else
                    return -1;*/
            }
        }

        public char CurrentChar { get { return (char) Current; } }

        public bool EndOfFile
        {
            get
            {
                if (this.IsParsingBackward)
                    return this.Position == -1;
                else
                    return this.Position == this.content.Length;
            }
        }

        public void SkipCharacters(byte[] characters)
        {
            while (!EndOfFile && characters.Contains((byte)this.Current))
                this.position += this.SeekDirection;
        }

        public void SkipWhitespaceCharacters()
        {
            this.SkipCharacters(whitespaceCharacters);
        }

        public void SkipNewLineCharacters()
        {
            this.SkipCharacters(newLineCharacters);
        }

        public string AsString(int index, int count)
        {
            var maxCount = this.content.Length - index;
            return Encoding.Default.GetString(this.content, index, Math.Min(count, maxCount));
        }

        public string AsStringByIndices(int beginPosition, int endPosition)
        {
            int count = endPosition - beginPosition + 1;
            return this.AsString(beginPosition, count);
        }

        public string ReadSeparatedData(byte[] separator)
        {
            int posA = this.position;

            while (!this.EndOfFile && !separator.Contains((byte)this.Current))
                this.position += this.SeekDirection;

            int posB = this.position - this.SeekDirection;

            //this.SkipCharacters(separator);

            int beginPosition = this.IsParsingBackward ? posB : posA;
            int endPosition = this.IsParsingBackward ? posA : posB;

            this.lastReadText = this.AsStringByIndices(beginPosition, endPosition);

            return this.lastReadText;
        }

        public string ReadLine()
        {
            this.SkipWhitespaceCharacters();
            return this.ReadSeparatedData(newLineCharacters);
        }

        public string ReadWord()
        {
            this.SkipWhitespaceCharacters();
            return this.ReadSeparatedData(delimiterCharacters);
        }

        public string ReadAsString(int count)
        {
            this.lastReadText = this.AsString(this.position, count);

            this.position += count;

            return this.lastReadText;
        }

        public bool TryReadString(string data)
        {
            return this.ReadAsString(data.Length) == data;
        }

        public bool TryReadToken(string token, bool restorePosition = false)
        {
            int originalPos = this.position;

            this.SkipWhitespaceCharacters();

            var result = TryReadString(token);
            if (!result || restorePosition)
                    this.Position = originalPos;

            return result;
        }

        public int ReadInt()
        {
            try { return int.Parse(this.ReadWord()); }
            catch (Exception e) { throw new PdfParsingException(this, "int parsing error", e); }
        }

        public double ReadDouble()
        {
            try { return double.Parse(this.ReadWord()); }
            catch (Exception e) { throw new PdfParsingException(this, "double parsing error", e); }
        }

        public byte[] ReadStream(int length)
        {
            var result = new byte[length];

            Array.Copy(this.content, this.position, result, 0, length);

            this.position += length;

            return result;
        }

        public void PositionCheck(string action = "processing file")
        {
            if (this.position == this.safeLastPosition)
                throw new PdfParsingException(this, "Position not changed from the last iteration -> infinity loop danger!");
            this.safeLastPosition = this.position;

            if (this.EndOfFile)
                throw new PdfParsingException(this, String.Format("Reached End of File while {0}!", action));
        }

        public void PositionCheckReset()
        {
            this.safeLastPosition = -1;
        }
    }

    public class PdfReader: FileReader
    {
        public PdfFile File;

        public PdfReader(byte[] content, PdfFile file): base(content)
        {
            this.File = file;
        }
    }
}
